﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Web;
using SlamCms.Common.Internal;

namespace SlamCms.Common
{
	public static class ImageExtensions
	{
		public static Image Crop(this Image image, Rectangle cropArea)
		{
			Ensure.ArgumentNotNull(image, "image");
			Ensure.ArgumentNotNull(cropArea, "cropArea");

			Image croppedImage = image;
			if (image.Width >= cropArea.Right && image.Height >= cropArea.Bottom)
			{
				var bmpImage = new Bitmap(image);
				croppedImage = bmpImage.Clone(cropArea, bmpImage.PixelFormat);
			}
			return croppedImage;
		}

		public static Image CropByAspectRatio(this Image image, int width, int height)
		{
			Ensure.ArgumentNotNull(image, "image");

			int xOffset = 0;
			int yOffset = 0;

			//double currentAspectRatio = ((double)(image.Width) / image.Height);
			//double aspectRatio = ((double)(width) / height);

			//if (aspectRatio <= currentAspectRatio)
			//{
			//    xOffset = (int)Math.Floor((decimal)((image.Width - width) / 2));
			//}
			//else
			//{	
			//    yOffset = (int)Math.Floor((decimal)((image.Height - height) / 2));
			//}

			Rectangle cropArea = new Rectangle(xOffset, yOffset, width, height);
			return Crop(image, cropArea);
		}

		public static Image Resize(this Image image, int width, int height)
		{
			Ensure.ArgumentNotOutOfRangeExclusive(width, 0, int.MaxValue, "width");
			Ensure.ArgumentNotOutOfRangeExclusive(height, 0, int.MaxValue, "height");
			return image.Resize(new Size(width, height));
		}

		public static Image Resize(this Image image, int width, int height, bool shrinkOnly)
		{
			Ensure.ArgumentNotOutOfRangeExclusive(width, 0, int.MaxValue, "width");
			Ensure.ArgumentNotOutOfRangeExclusive(height, 0, int.MaxValue, "height");
			return image.Resize(new Size(width, height), shrinkOnly);
		}

		public static Image Resize(this Image image, Size newSize)
		{
			Ensure.ArgumentNotNull(image, "image");
			Ensure.ArgumentNotNull(newSize, "newSize");
			return image.Resize(newSize, true);
		}

		public static Image Resize(this Image image, Size newSize, bool shrinkOnly)
		{
			Ensure.ArgumentNotNull(image, "image");
			Ensure.ArgumentNotNull(newSize, "newSize");

			Image target = image;
			bool resizeImage = shrinkOnly ? (newSize.Width < image.Width || newSize.Height < image.Height) : true;
			if (resizeImage)
			{
				var size = CalculateScaledMinSize(image.Size, newSize);
				target = new Bitmap(size.Width, size.Height);
				using (var graphic = Graphics.FromImage(target))
				{
					if (image.Palette.Entries.Length == 0)
					{
						graphic.CompositingQuality = CompositingQuality.HighQuality;
						graphic.SmoothingMode = SmoothingMode.HighQuality;
						graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
					}
					graphic.DrawImage(image, 0, 0, size.Width, size.Height);
				}
			}
			return target;
		}

		public static Image ResizeFit(this Image image, int width, int height)
		{
			Ensure.ArgumentNotOutOfRangeExclusive(width, 0, int.MaxValue, "width");
			Ensure.ArgumentNotOutOfRangeExclusive(height, 0, int.MaxValue, "height");
			return image.ResizeFit(new Size(width, height));
		}

		public static Image ResizeFit(this Image image, int width, int height, bool shrinkOnly)
		{
			Ensure.ArgumentNotOutOfRangeExclusive(width, 0, int.MaxValue, "width");
			Ensure.ArgumentNotOutOfRangeExclusive(height, 0, int.MaxValue, "height");
			return image.ResizeFit(new Size(width, height), shrinkOnly);
		}

		public static Image ResizeFit(this Image image, Size newSize)
		{
			Ensure.ArgumentNotNull(image, "image");
			Ensure.ArgumentNotNull(newSize, "newSize");
			return image.ResizeFit(newSize, true);
		}

		public static Image ResizeFit(this Image image, Size newSize, bool shrinkOnly)
		{
			Ensure.ArgumentNotNull(image, "image");
			Ensure.ArgumentNotNull(newSize, "newSize");

			Image target = image;
			bool resizeImage = shrinkOnly ? (newSize.Width < image.Width || newSize.Height < image.Height) : true;
			if (resizeImage)
			{
				var size = CalculateScaledMaxSize(image.Size, newSize);
				target = new Bitmap(size.Width, size.Height);
				using (var graphic = Graphics.FromImage(target))
				{
					if (image.Palette.Entries.Length == 0)
					{
						graphic.CompositingQuality = CompositingQuality.HighQuality;
						graphic.SmoothingMode = SmoothingMode.HighQuality;
						graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
					}
					graphic.DrawImage(image, 0, 0, size.Width, size.Height);
				}
			}
			return target;
		}

		public static void SaveAutoSelectEncoder(this Image image, string path)
		{
			SaveAutoSelectEncoder(image, path, 100L);
		}

		public static void SaveAutoSelectEncoder(this Image image, string path, long jpegQuality)
		{
			if (image.RawFormat.Guid == ImageFormat.Jpeg.Guid)
			{
				image.SaveJpeg(path, jpegQuality);
			}
			else if (image.RawFormat.Guid == ImageFormat.Gif.Guid)
			{
				image.SaveGif(path);
			}
			else
			{
				image.Save(path, image.RawFormat);
			}
		}

		public static void SaveAutoSelectEncoder(this Image image, Stream stream)
		{
			SaveAutoSelectEncoder(image, stream, 100L);
		}

		public static void SaveAutoSelectEncoder(this Image image, Stream stream, long jpegQuality)
		{
			if (image.RawFormat.Guid == ImageFormat.Jpeg.Guid)
			{
				image.SaveJpeg(stream, jpegQuality);
			}
			else if (image.RawFormat.Guid == ImageFormat.Gif.Guid)
			{
				image.SaveGif(stream);
			}
			else
			{
				image.Save(stream, image.RawFormat);
			}
		}

		public static void SaveGif(this Image image, string path)
		{
			SaveGif(image, path, image.Palette.Entries.Length > 255 ? 255 : image.Palette.Entries.Length);
		}

		public static void SaveGif(this Image image, string path, int maxColors)
		{
			OctreeQuantizer quantizer = new OctreeQuantizer(maxColors, 8);
			using (Bitmap quantizedImage = quantizer.Quantize(image))
			{
				quantizedImage.Save(path, ImageFormat.Gif);
			}
		}

		public static void SaveGif(this Image image, Stream stream)
		{
			SaveGif(image, stream, image.Palette.Entries.Length > 255 ? 255 : image.Palette.Entries.Length);
		}

		public static void SaveGif(this Image image, Stream stream, int maxColors)
		{
			OctreeQuantizer quantizer = new OctreeQuantizer(maxColors, 8);
			using (Bitmap quantizedImage = quantizer.Quantize(image))
			{
				quantizedImage.Save(stream, ImageFormat.Gif);
			}
		}

		public static void SaveJpeg(this Image image, string path, long quality)
		{
			var qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);

			var jpegCodec = GetEncoderInfo("image/jpeg");

			if (jpegCodec == null)
				return;

			var encoderParams = new EncoderParameters(1);
			encoderParams.Param[0] = qualityParam;

			image.Save(path, jpegCodec, encoderParams);
		}

		public static void SaveJpeg(this Image image, Stream stream, long quality)
		{
			var qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);

			var jpegCodec = GetEncoderInfo("image/jpeg");

			if (jpegCodec == null)
				return;

			var encoderParams = new EncoderParameters(1);
			encoderParams.Param[0] = qualityParam;

			image.Save(stream, jpegCodec, encoderParams);
		}



		public static byte[] ToBytes(this Image image, ImageFormat format)
		{
			Ensure.ArgumentNotNull(image, "image");
			Ensure.ArgumentNotNull(format, "format");

			using (var stream = new MemoryStream())
			{
				image.Save(stream, format);
				return stream.ToArray();
			}
		}

		public static Stream ToStream(this Image image, ImageFormat format)
		{
			Ensure.ArgumentNotNull(image, "image");
			Ensure.ArgumentNotNull(format, "format");

			var stream = new MemoryStream();
			image.Save(stream, format);
			stream.Position = 0;
			return stream;
		}

		private static ImageCodecInfo GetEncoderInfo(string mimeType)
		{
			Ensure.ArgumentNotNullOrEmpty(mimeType, "mimeType");

			var codecs = ImageCodecInfo.GetImageEncoders();

			for (var i = 0; i < codecs.Length; i++)
				if (codecs[i].MimeType == mimeType)
					return codecs[i];

			return null;
		}

		private static Size CalculateScaledMinSize(Size image, Size boundSize)
		{
			double widthScale = 0, heightScale = 0;
			if (image.Width != 0)
				widthScale = (double)boundSize.Width / (double)image.Width;
			if (image.Height != 0)
				heightScale = (double)boundSize.Height / (double)image.Height;

			var scale = Math.Min(widthScale, heightScale);

			return new Size((int)(image.Width * scale), (int)(image.Height * scale));
		}

		private static Size CalculateScaledMaxSize(Size image, Size boundSize)
		{
			double widthScale = 0, heightScale = 0;
			if (image.Width != 0)
				widthScale = (double)boundSize.Width / (double)image.Width;
			if (image.Height != 0)
				heightScale = (double)boundSize.Height / (double)image.Height;

			var scale = Math.Max(widthScale, heightScale);

			return new Size((int)(image.Width * scale), (int)(image.Height * scale));
		}

		public static bool WriteResizedImageToDisk(this Image img, string filePath, int targetWidth, int targetHeight)
		{
			return img.WriteResizedImageToDisk(filePath, targetWidth, targetHeight, 75L);
		}

		public static bool WriteResizedImageToDisk(this Image img, string filePath, int targetWidth, int targetHeight, long imageQuality)
		{
			var widthRatio = ((float)img.Size.Width) / ((float)targetWidth);
			var heightRatio = ((float)img.Size.Height) / ((float)targetHeight);

			var targetW = 0;
			var targetH = 0;
			var offsetX = 0;
			var offsetY = 0;

			if (widthRatio <= heightRatio)
			{
				targetW = img.Size.Width;
				targetH = (int)(((float)targetHeight) * widthRatio);
				offsetY = (img.Size.Height - targetH) / 2;
			}
			else
			{
				targetW = (int)(((float)targetWidth) * heightRatio);
				targetH = img.Size.Height;
				offsetX = (img.Size.Width - targetW) / 2;
			}

			Image croppedImage = new Bitmap(targetW, targetH, PixelFormat.Format32bppArgb);
			Graphics f = Graphics.FromImage(croppedImage);
			f.CompositingQuality = CompositingQuality.HighQuality;
			f.SmoothingMode = SmoothingMode.HighQuality;
			f.InterpolationMode = InterpolationMode.HighQualityBicubic;
			PointF p1 = new PointF(0, 0);
			PointF p2 = new PointF(targetW, 0);
			PointF p3 = new PointF(0, targetH);
			PointF[] points = { p1, p2, p3 };
			RectangleF sourceR = new RectangleF(offsetX, offsetY, targetW, targetH);

			f.DrawImage(img, points, sourceR, GraphicsUnit.Pixel);

			System.Drawing.Image finalImage = new Bitmap(targetWidth, targetHeight, PixelFormat.Format32bppArgb);
			Graphics g = Graphics.FromImage(finalImage);
			g.CompositingQuality = CompositingQuality.HighQuality;
			g.SmoothingMode = SmoothingMode.HighQuality;
			g.InterpolationMode = InterpolationMode.HighQualityBicubic;
			Rectangle r = new Rectangle(0, 0, targetWidth, targetHeight);
			g.DrawImage(croppedImage, r);

			ImageCodecInfo codecInfo = GetEncoderInfo("image/jpeg");
			EncoderParameters eParams = new EncoderParameters(1);

			long q = imageQuality;
			eParams.Param[0] = new EncoderParameter(Encoder.Quality, q);

			try
			{
				finalImage.Save(HttpContext.Current.Server.MapPath(filePath), codecInfo, eParams); //only meant to be used within a web app. todo: support multiple clients
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
}